package com.mengfou;

/**
 * @author 梦否
 * @date 2025/07/05 13:42
 */

import java.io.*;
import java.net.*;
import java.util.ArrayList;
import java.util.Date;
import java.util.Enumeration;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * @author 梦否 on 2024/4/14
 * @blog https://mengfou.blog.csdn.net/
 */
public class HttpServerHelper {
    public static class Builder {
        private final BaseServer mHttpServer;

        public Builder(){
            mHttpServer = new BaseServerImpl();
        }

        public Builder setPort(int port) {
            mHttpServer.mPort = port;
            return this;
        }

        public Builder setInetAddress(InetAddress address) {
            this.mHttpServer.address = address;
            return this;
        }

        public Builder setOnCloseListener(CloseCallBack callBack) {
            mHttpServer.callBack = callBack;
            return this;
        }
        public BaseServer build() {
            if(mHttpServer.address == null) {
                try{
                    mHttpServer.address = InetAddress.getLocalHost();
                } catch (UnknownHostException e) {
                    return null;
                }
            }
            InetAddress inetAddress;
            if(mHttpServer.address.isLoopbackAddress() && (inetAddress = getInetAddress()) != null) {
                mHttpServer.address = inetAddress;
            }
            return mHttpServer;
        }

        private InetAddress getInetAddress() {
            try {
                Enumeration<NetworkInterface> networkInterfaces = NetworkInterface.getNetworkInterfaces();
                while(networkInterfaces.hasMoreElements()) {
                    NetworkInterface networkInterface = networkInterfaces.nextElement();
                    Enumeration<InetAddress> inetAddresses = networkInterface.getInetAddresses();
                    while(inetAddresses.hasMoreElements()) {
                        InetAddress inetAddress = inetAddresses.nextElement();
                        if(!inetAddress.isLoopbackAddress() && inetAddress instanceof Inet4Address) {
                            return inetAddress;
                        }
                    }
                }
            } catch (SocketException e) {
                e.printStackTrace();
            }
            return null;
        }
    }

    public static abstract class BaseServer implements Runnable {
        InetAddress address;
        int mPort = 8080;
        ServerSocket mServer;
        CloseCallBack callBack;
    }

    public interface CloseCallBack {
        void onClose();
    }

    public static class ServerManager {
        private ExecutorService mThreadPool;
        private BaseServer mHttpServer;

        public void init() {
            mHttpServer = new Builder().setOnCloseListener(new CloseCallBack() {
                @Override
                public void onClose() {
                    mThreadPool.shutdownNow();
                }
            }).build();
            mThreadPool = Executors.newFixedThreadPool(1);
        }

        public void start() {
            if(mHttpServer != null) {
                mThreadPool.execute(mHttpServer);
            }
        }
    }

    public enum ResponseType {
        STRING,
        FILE
    }

    public static class BaseServerImpl extends BaseServer {
        private static final String FINAL_STR_GET = "GET";
        private static final String FINAL_STR_BLACK = " ";
        // 待响应文件的路径
        private static final String FINAL_STR_FILE_PATH = "";
        private static final String FINAL_STR_NEXT_LINE_WEB = "\r\n";
        private static final String FINAL_STR_CONTENT_END = FINAL_STR_NEXT_LINE_WEB + FINAL_STR_NEXT_LINE_WEB;
        private static final String FINAL_STR_NEXT_LINE = "\n";
        private static final String FINAL_STR_HEADER_OK = "HTTP/1.1 200 OK";
        private static final String FINAL_STR_HEADER_CONTENT_TYPE_TEXT_PLAIN = "Content-Type: text/plain";
        private static final String FINAL_STR_BYE = "Bye!";
        private static final String FINAL_STR_HEADER_HELLO = "Hello Client!";

        public BaseServerImpl() {
            try {
                mServer = new ServerSocket(this.mPort, 10, this.address);
            } catch (IOException e) {
                e.printStackTrace();
            }


        }

        private void print(String ...logInfo) {
            StringBuilder stringBuilder = new StringBuilder();
            for (String s : logInfo) {
                stringBuilder.append(s).append(",");
            }
            Log.e("ServerInfo", stringBuilder.toString());
        }

        @Override
        public void run() {
            print("http://" + this.address.getHostAddress() +":"+mPort+ " u can use [hello/file/bye]");
            while(true) {
                try{
                    Socket client = this.mServer.accept();
                    InputStream inputStream = client.getInputStream();
                    RequestCommandType processType = getProcessType(inputStream);
                    if(processType == RequestCommandType.COMMAND_HELLO) {
                        print("[GET] /hello");
                        httpResponse(client, ResponseType.STRING, null, FINAL_STR_HEADER_HELLO.getBytes());
                    } else if(processType == RequestCommandType.COMMAND_FILE) {
                        print("[GET] /file");
                        File file = new File(FINAL_STR_FILE_PATH);
                        if(!file.exists()) {
                            httpResponse(client, ResponseType.STRING, null, "file not exist!".getBytes());
                            continue;
                        }
                        String header = FINAL_STR_HEADER_OK + FINAL_STR_NEXT_LINE_WEB +
                                "Content-Disposition: attachment; filename=\"" + file.getName() + "\"" + FINAL_STR_NEXT_LINE_WEB +
                                "Content-Type: application/octet-stream" + FINAL_STR_NEXT_LINE_WEB +
                                "Content-Length: " + file.length() + FINAL_STR_NEXT_LINE_WEB +
                                FINAL_STR_NEXT_LINE_WEB;
                        FileInputStream fileInputStream = new FileInputStream(file);
                        int len = -1;
                        byte[] datas = new byte[1024];
                        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
                        while((len = fileInputStream.read(datas)) != -1) {
                            byteArrayOutputStream.write(datas, 0, len);
                        }
                        httpResponse(client, ResponseType.FILE, header, byteArrayOutputStream.toByteArray());
                        byteArrayOutputStream.close();
                    } else if(processType == RequestCommandType.COMMAND_CLOSE) {
                        print("[GET] /bye");
                        httpResponse(client, ResponseType.STRING, null, FINAL_STR_BYE.getBytes());
                        break;
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }

        private void httpResponse(Socket client, ResponseType responseType, String header, byte[] content) throws IOException{
            Date today = new Date();
            String response = null;
            if(responseType == ResponseType.STRING && header == null) {
                response = FINAL_STR_HEADER_OK + FINAL_STR_NEXT_LINE_WEB +
                        FINAL_STR_HEADER_CONTENT_TYPE_TEXT_PLAIN + FINAL_STR_NEXT_LINE_WEB +
                        FINAL_STR_NEXT_LINE_WEB
                        + today + FINAL_STR_NEXT_LINE_WEB +
                        FINAL_STR_NEXT_LINE_WEB;
            }
            if(responseType == ResponseType.FILE) {
                response = header;
            }

            if(response == null) {
                throw new RuntimeException("header can't be null.");
            }

            OutputStream outputStream = client.getOutputStream();
            outputStream.write(response.getBytes());
            outputStream.write(content);
            outputStream.flush();
            outputStream.close();
        }

        private RequestCommandType getProcessType(InputStream inputStream) throws IOException{
            int len = -1;
            byte[] buffer = new byte[1024];
            List<String> contentList = new ArrayList<String>();
            while((len = inputStream.read(buffer)) != -1) {
                String currentContent = new String(buffer, 0, len);
                contentList.add(currentContent);
                if(currentContent.endsWith(FINAL_STR_CONTENT_END)) {
                    break;
                }
            }

            StringBuilder content = new StringBuilder();
            for (String s : contentList) {
                content.append(s);
            }

            String[] headerLines = content.toString().split(FINAL_STR_NEXT_LINE);
            for (String headerLine : headerLines) {
                if(headerLine.startsWith(FINAL_STR_GET)) {
                    String[] split = headerLine.split(FINAL_STR_BLACK);
                    if(split.length != 3) {
                        return null;
                    }
                    return RequestCommandType.convert(split[1].substring(1));
                }
            }
            return null;
        }
    }

    public enum RequestCommandType {
        COMMAND_HELLO("hello"),
        COMMAND_FILE("file"),
        COMMAND_CLOSE("bye");

        private final String val;

        RequestCommandType(String val) {
            this.val = val;
        }

        public static RequestCommandType convert(String value) {
            for (RequestCommandType requestCommandType : RequestCommandType.values()) {
                if(requestCommandType.val.equalsIgnoreCase(value)) {
                    return requestCommandType;
                }
            }
            return null;
        }
    }
}
